package code.example.base.util.concurrent.lock.countdownlatch;



import code.example.tool.LogFactory;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.concurrent.locks.LockSupport;

public class ICountDownLatchCas implements ICountDownLatch {
    volatile  int count = 0;
    AtomicIntegerFieldUpdater countAtomic = AtomicIntegerFieldUpdater.newUpdater(ICountDownLatchCas.class,"count");
    volatile Node head = null;
    AtomicReferenceFieldUpdater headUpdater = AtomicReferenceFieldUpdater.newUpdater(ICountDownLatchCas.class,Node.class,"head");
    public ICountDownLatchCas(int count){
        countAtomic.lazySet(this,count);
        head = new Node(null);
    }
    @Override
    public void countdown() {
       int num =   countAtomic.decrementAndGet(this);
        LogFactory.getLog().info("countdown:"+Thread.currentThread().getName()+" :count down :"+num);
       if(num <=0){
           LogFactory.getLog().info(Thread.currentThread().getName()+" :to wake up");
           wakeUp();
       }
    }

    private void wakeUp(){
        for(;;){
            Node head = this.head;
            if(head == null){
                return;
            }else if(headUpdater.compareAndSet(this,head,null)){
                wakeUpNode(head);
                return;
            }
        }
    }
    private void wakeUpNode(Node node){
        if(node != null){
            Thread thread = node.thread;
            if(thread != null){
                LogFactory.getLog().info("to wake up:  unpark thread: "+thread.getName());
                LockSupport.unpark(thread);
                Node next = node.next;
                wakeUpNode(next);
            }
        }
    }
    private boolean addWaiter(Node head,Node waitNode){
        if(headUpdater.compareAndSet(this,head,waitNode)){
            LogFactory.getLog().info("await :to block thread: to park thread: "+waitNode.thread.getName());
            waitNode.next = head;
            return true;
        }else{
            return false;
        }
    }
    @Override
    public void await() {
        await(0,TimeUnit.SECONDS);
    }

    @Override
    public boolean  await(int numer, TimeUnit timeUnit) {
        Node waitNode=null;
        boolean inWaitQueue= false;

        long deadLine =  System.currentTimeMillis()+timeUnit.toMillis(numer);
        for(;;){
            Node head = this.head;
            int num =   countAtomic.get(this);
            if(num<=0){
                return true;
            }else if(head == null){
                continue;
            } if(waitNode == null){
                waitNode = new Node();
            }else if(!inWaitQueue){
                inWaitQueue =  addWaiter(head,waitNode);
            }else if(numer==0){
                LockSupport.park();
            }else{
                LockSupport.parkUntil(deadLine);
                long currentTime =System.currentTimeMillis();
                if(currentTime>=deadLine){
                    return false;
                }
            }
        }
    }

    class Node{
        Thread thread = null;
        Node next = null;
        public Node(){
            this.thread = Thread.currentThread();
        }
        public Node (Thread thread){
            this.thread = thread;
        }
    }
}
